package main

import (
	"fmt"
	"sync"
)

/*
*
1互斥锁Mutex
有时候在Go代码中可能会有多个goroutine 同时操作一个资源（临界区），这种情况会发生竞态问题（数据静态）。
*/

//var x int64
//var wg sync.WaitGroup
//
//func add() {
//	for i := 0; i < 5000; i++ {
//		time.Sleep(10)
//		x = x + 1
//	}
//	wg.Done()
//}
//func main() {
//	wg.Add(2) //将计数器的值设为2，以下开启了两个协程
//	go add()
//	go add()
//	wg.Wait() //两个协程都结束之前一直阻塞，直到两个协程都运行结束才继续执行
//	fmt.Println(x)
//}

/**
上面的代码中我们开启了两个 goroutine 去累加变量 x 的值，这两个 goroutine 在访问和修改 x 变量的时候就会存在数据竞争，导致最后的结果与期待的不符。

为了避免这种情况，互斥锁是一种常用的 控制共享资源访问 的方法，它能够保证 同时只有一个 goroutine 可以访问共享资源。Go语言中使用 sync 包的 Mutex 类型来实现互斥锁。

使用互斥锁能够保同一时间内只有一个goroutine进入临界区，（临界区是加锁和解锁之间的区域），其他的goroutine则在等待所，当互斥锁释放之后，等待的goroutine才可以获取锁进入临界区。
多个goroutine同时等待一个锁的时候，唤醒的策略是随机的。

上面的代码经过互斥锁优化之后如下：
*/

var x int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
	for i := 0; i < 5000; i++ {
		lock.Lock()   // 加锁
		x = x + 1     //每次只有一个 goroutine 能对 x 进行 +1 操作
		lock.Unlock() // 解锁
	}
	wg.Done()
}
func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(x)
}

/**
互斥锁的三个状态：
互斥锁的状态使用 int32 来表示的，但是锁的尊贵太并不是互斥的，他的最低三位分表表示 mutexLocked、mutexWoken 和 mutexStaving
剩下的位置都用来标识当前有多少个 goroutine 等待互斥锁被释放。
内部实现时把改变来那个（state int32） 分成四份，用于记录Mutex 的四种状态：
Locked 表示该Mutex是否已经被锁定，0:没有锁定，1:已经锁定。
Woken 表示是否有协程已经被唤醒，0:没有被唤醒，1：已经被唤醒，正在加锁的过程中。
Starving 表示是否有协程在饥饿状态，0:没有饥饿，1：饥饿状态，说明有协程阻塞超过了1ms。
Waiter 表示阻塞等待该锁的协程个数，协程甲所示根据此值来判断是否需要释放信号量。


互斥锁在被创建出来时，所有的状态位的默认值都是 0，当互斥锁被锁定时 mutexLocked 就会被置成 1、当互斥锁被在正常模式下被唤醒时 mutexWoken
就会被被置成 1、mutexStarving 用于表示当前的互斥锁是否进入饥饿状态，最后的几位是在当前互斥锁上等待的 Goroutine 个数。

协程之间 抢锁 实际上是抢给 Locked 赋值的权利，能给Locked域置1，就说明抢锁成功。抢不到的话就阻塞等待Mutex.sema 信号量，
一旦持有锁的协程解锁，等待的协程会依次被唤醒。

*/
